/*=====================================================================*
 *                   Copyright (C) 2011 Paul Mineiro                   *
 * All rights reserved.                                                *
 *                                                                     *
 * Redistribution and use in source and binary forms, with             *
 * or without modification, are permitted provided that the            *
 * following conditions are met:                                       *
 *                                                                     *
 *     * Redistributions of source code must retain the                *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer.                                       *
 *                                                                     *
 *     * Redistributions in binary form must reproduce the             *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer in the documentation and/or            *
 *     other materials provided with the distribution.                 *
 *                                                                     *
 *     * Neither the name of Paul Mineiro nor the names                *
 *     of other contributors may be used to endorse or promote         *
 *     products derived from this software without specific            *
 *     prior written permission.                                       *
 *                                                                     *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND              *
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,         *
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES               *
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE             *
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER               *
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,                 *
 * INCIDENTAL, SPECIAL, EXDATAPLARY, OR CONSEQUENTIAL DAMAGES            *
 * (INCLUDING, BUT NOT LIMITED TO, PROCURDATAENT OF SUBSTITUTE           *
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR                *
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF          *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT           *
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY              *
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE             *
 * POSSIBILITY OF SUCH DAMAGE.                                         *
 *                                                                     *
 * Contact: Paul Mineiro <paul@mineiro.com>                            *
 *=====================================================================*/

#ifndef __PARSE_HH__
#define __PARSE_HH__

#include "MurmurHash3.hh"
#include "feature.hh"
#include "rating.hh"

#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdint.h>
#include <vector>

namespace playerpiano
{

inline uint64_t
hash (const char* s,
      uint64_t    seed)
{
  uint64_t hv[2];
  const char* p;

  for (hv[0] = 0, p = s; isdigit (*p); ++p)
    {
      hv[0] *= 10;
      hv[0] += *p - '0';
    }
  
  if (*p == '\0')
    {
      return hv[0] + seed;
    }

  MurmurHash3_x64_128 (s, strlen (s), seed, hv);
  return hv[0];
}

inline char*
eat_white (char* p)
{
  while (*p != '\0' && isspace (*p))
    {
      ++p;
    }

  return p;
}

inline char*
eat_nonwhite (char* p)
{
  while (*p != '\0' && ! isspace (*p))
    {
      ++p;
    }

  return p;
}

inline int
parse_unsigned_int (char*        p,
                    unsigned int maxval)
{
  char* endptr;
  int val = strtol (p, &endptr, 0);

  if (*endptr == '\0')
    {
      if (val >= 0 && val < (int) maxval)
        {
          return val;
        }
    }

  return -1;
}

inline float
parse_non_negative_float (char* p)
{
  char* endptr;
  float val = strtof (p, &endptr);

  if (*endptr == '\0')
    {
      if (val >= 0)
        {
          return val;
        }
    }

  return -1;
}

int
parse_ratings (char*                    bufptr,
               std::vector<Rating>&     ratings,
               int&                     clamp,
               unsigned int             n_labels,
               unsigned int             n_workers)
{
  while (*bufptr != '\0')
    {
      char* comma = strchr (bufptr, ',');

      if (comma)
        {
          *comma = '\0';
        }

      char* colon = strrchr (bufptr, ':');

      if (! colon)
        {
          if (clamp >= 0)
            {
              return -1;
            }

          clamp = parse_unsigned_int (bufptr, n_labels);

          if (clamp < 0)
            {
              return -1;
            }
        }
      else
        {
          *colon = '\0';

          unsigned int worker = hash (bufptr, 0) % n_workers;
          int label = parse_unsigned_int (colon + 1, n_labels);

          if (label < 0)
            {
              return -1;
            }

          Rating rating = { worker, label };

          ratings.push_back (rating);
        }

      if (! comma)
        {
          return 0;
        }
      
      bufptr = comma + 1;
    }

  return -1;
}

/* (worker:label,)* weight tag|space0 feature0:val0 ... |space1 ...*/

int
parse_input_line (FILE*                    in,
                  std::vector<Rating>&     ratings,
                  IndexedFeatureSet&       feature_set,
                  float&                   importance_weight,
                  char*                    tag,
                  unsigned int             max_tag,
                  int&                     clamp,
                  unsigned int             n_labels,
                  unsigned int             n_workers)
{
  char buf[65536];

  if (! fgets (buf, sizeof (buf), in))
    {
      return -1;
    }

  char* bufptr = buf;

  ratings.resize (0);
  feature_set.reset ();
  clamp = -1;

  if (! isspace (*bufptr))
    {
      char* space = strchr (bufptr, ' ');

      if (! space)
        {
          return -1;
        }

      *space = '\0';
      if (parse_ratings (bufptr, ratings, clamp, n_labels, n_workers))
        {
          return -1;
        }

      bufptr = space + 1;
    }

  bufptr = eat_white (bufptr);
  char* pipe = strchr (bufptr, '|');

  if (! pipe)
    {
      return -1;
    }

  *pipe = '\0';

  char* space = strchr (bufptr, ' ');

  if (space)
    {
      *space = '\0';
      importance_weight = parse_non_negative_float (bufptr);

      if (importance_weight < 0)
        {
          return -1;
        }

      bufptr = space + 1;
    }
  else
    {
      importance_weight = 1;
    }

  strncpy (tag, bufptr, max_tag);
  tag[max_tag - 1] = '\0';

  bufptr = pipe + 1;

  while (*bufptr != '\0')
    { 
      const char* ns = "";
      float nsweight = 1;

      if (! isspace (*bufptr))
        { 
          ns = bufptr;
          bufptr = eat_nonwhite (bufptr);
          if (*bufptr == '\0') { break; }
          *bufptr++ = '\0';

          char* colon = strrchr (const_cast<char*> (ns), ':');

          if (colon)
            { 
              char* endptr;

              *colon++ = '\0';
              nsweight = strtof (colon, &endptr);

              if (*colon == '\0' || *endptr != '\0')
                { 
                  return -1;
                }
            }
        }

      uint64_t seed = hash (ns, 0);

      bufptr = eat_white (bufptr);
      if (*bufptr == '\0') { break; }

      while (*bufptr != '|' && *bufptr != '\0')
        { 
          char* feat = bufptr;
          float weight = 1;

          bufptr = eat_nonwhite (bufptr);
          if (*bufptr != '\0') { *bufptr++ = '\0'; }

          char* colon = strrchr (feat, ':');

          if (colon)
            { 
              char* endptr;

              *colon++ = '\0';
              weight = strtof (colon, &endptr);

              if (*colon == '\0' || *endptr != '\0')
                { 
                  return -1;
                }
            }

          uint64_t hv = hash (feat, seed);
          unsigned int n = static_cast<unsigned char> (ns[0]);

          Feature feature = { hv, weight * nsweight };

          feature_set[n].push_back (feature);
        }

      if (*bufptr == '|')
        { 
          ++bufptr;
        }
    }
  
  return 0;
}

}

#endif /* __PARSE_HH__ */
